/*-------------------------------------------------------------------------
 *
 * restrictinfo.c
 *	  RestrictInfo 节点操作例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/util/restrictinfo.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "optimizer/restrictinfo.h"


static RestrictInfo *fc_make_restrictinfo_internal(PlannerInfo *fc_root,
												Expr *fc_clause,
												Expr *fc_orclause,
												bool fc_is_pushed_down,
												bool fc_outerjoin_delayed,
												bool fc_pseudoconstant,
												Index fc_security_level,
												Relids fc_required_relids,
												Relids fc_outer_relids,
												Relids fc_nullable_relids);
static Expr *fc_make_sub_restrictinfos(PlannerInfo *fc_root,
									Expr *fc_clause,
									bool fc_is_pushed_down,
									bool fc_outerjoin_delayed,
									bool fc_pseudoconstant,
									Index fc_security_level,
									Relids fc_required_relids,
									Relids fc_outer_relids,
									Relids fc_nullable_relids);


/*
 * make_restrictinfo
 *
 * 构建一个包含给定子表达式的 RestrictInfo 节点。
 *
 * RestrictInfo 的 is_pushed_down、outerjoin_delayed 和 pseudoconstant 标志必须由调用者提供，以及 security_level、outer_relids 和 nullable_relids 的正确值。
 * required_relids 可以为 NULL，此时它默认为实际条款内容（即 clause_relids）。
 *
 * 我们初始化仅依赖于给定子表达式的字段，将依赖于上下文（或可能根本不需要）的其他字段留到后面填写。
 */
RestrictInfo * make_restrictinfo(PlannerInfo *fc_root,
				  Expr *fc_clause,
				  bool fc_is_pushed_down,
				  bool fc_outerjoin_delayed,
				  bool fc_pseudoconstant,
				  Index fc_security_level,
				  Relids fc_required_relids,
				  Relids fc_outer_relids,
				  Relids fc_nullable_relids)
{
	/*
	 * 如果是 OR 条件，构建一个修改的副本，在每个顶层 AND/OR 结构的子条件上插入 RestrictInfos。
	 */
	if (is_orclause(fc_clause))
		return (RestrictInfo *) fc_make_sub_restrictinfos(fc_root,
													   fc_clause,
													   fc_is_pushed_down,
													   fc_outerjoin_delayed,
													   fc_pseudoconstant,
													   fc_security_level,
													   fc_required_relids,
													   fc_outer_relids,
													   fc_nullable_relids);

	/* 不应该是 AND 条件，否则 AND/OR 展平会搞乱 */
	Assert(!is_andclause(fc_clause));

	return fc_make_restrictinfo_internal(fc_root,
									  fc_clause,
									  NULL,
									  fc_is_pushed_down,
									  fc_outerjoin_delayed,
									  fc_pseudoconstant,
									  fc_security_level,
									  fc_required_relids,
									  fc_outer_relids,
									  fc_nullable_relids);
}

/*
 * make_restrictinfo_internal
 *
 * 主要入口点和递归情况的通用代码。
 */
static RestrictInfo * fc_make_restrictinfo_internal(PlannerInfo *fc_root,
						   Expr *fc_clause,
						   Expr *fc_orclause,
						   bool fc_is_pushed_down,
						   bool fc_outerjoin_delayed,
						   bool fc_pseudoconstant,
						   Index fc_security_level,
						   Relids fc_required_relids,
						   Relids fc_outer_relids,
						   Relids fc_nullable_relids)
{
	RestrictInfo *fc_restrictinfo = makeNode(RestrictInfo);

	fc_restrictinfo->clause = fc_clause;
	fc_restrictinfo->orclause = fc_orclause;
	fc_restrictinfo->is_pushed_down = fc_is_pushed_down;
	fc_restrictinfo->outerjoin_delayed = fc_outerjoin_delayed;
	fc_restrictinfo->pseudoconstant = fc_pseudoconstant;
	fc_restrictinfo->can_join = false; /* 可能会在下面设置 */
	fc_restrictinfo->security_level = fc_security_level;
	fc_restrictinfo->outer_relids = fc_outer_relids;
	fc_restrictinfo->nullable_relids = fc_nullable_relids;

	/*
	 * 如果它可能被下层安全条件延迟，判断它是否是防泄漏的。我们可以跳过对零级条件的测试，因为它们根本不会因安全原因延迟。
	 */
	if (fc_security_level > 0)
		fc_restrictinfo->leakproof = !contain_leaked_vars((Node *) fc_clause);
	else
		fc_restrictinfo->leakproof = false;	/* 实际上，“不知道” */

	/*
	 * 将波动性标记为未知。contain_volatile_functions 函数将在首次调用此 RestrictInfo 时确定是否存在任何波动函数。
	 */
	fc_restrictinfo->has_volatile = VOLATILITY_UNKNOWN;

	/*
	 * 如果是二元操作条件，设置左/右 relids 信息。无论如何，设置总条款 relids 信息。
	 */
	if (is_opclause(fc_clause) && list_length(((OpExpr *) fc_clause)->args) == 2)
	{
		fc_restrictinfo->left_relids = pull_varnos(fc_root, get_leftop(fc_clause));
		fc_restrictinfo->right_relids = pull_varnos(fc_root, get_rightop(fc_clause));

		fc_restrictinfo->clause_relids = bms_union(fc_restrictinfo->left_relids,
												fc_restrictinfo->right_relids);

		/*
		 * 它看起来像一个普通的连接条件，即，二元操作符将来自不同关系的表达式关联起来？如果是这样，我们可能能够在连接算法中使用它。请注意，这只是一个纯粹的语法测试，无论上下文如何都会进行。
		 */
		if (!bms_is_empty(fc_restrictinfo->left_relids) &&
			!bms_is_empty(fc_restrictinfo->right_relids) &&
			!bms_overlap(fc_restrictinfo->left_relids,
						 fc_restrictinfo->right_relids))
		{
			fc_restrictinfo->can_join = true;
			/* pseudoconstant 当然不应该为真 */
			Assert(!fc_restrictinfo->pseudoconstant);
		}
	}
	else
	{
		/* 不是二元操作条件，因此将左/右 relid 集标记为空 */
		fc_restrictinfo->left_relids = NULL;
		fc_restrictinfo->right_relids = NULL;
		/* 并以困难的方式获取总 relid 集 */
		fc_restrictinfo->clause_relids = pull_varnos(fc_root, (Node *) fc_clause);
	}

	/* required_relids 默认为 clause_relids */
	if (fc_required_relids != NULL)
		fc_restrictinfo->required_relids = fc_required_relids;
	else
		fc_restrictinfo->required_relids = fc_restrictinfo->clause_relids;

	/*
	 * 用 “尚未设置” 标记填充所有可缓存字段。除非需要，否则这些都不会被计算。特别注意，我们在这里不标记二元操作条件为可合并连接或哈希连接；这只有在出现在连接条件列表的正确上下文中时才会发生。
	 */
	fc_restrictinfo->parent_ec = NULL;

	fc_restrictinfo->eval_cost.startup = -1;
	fc_restrictinfo->norm_selec = -1;
	fc_restrictinfo->outer_selec = -1;

	fc_restrictinfo->mergeopfamilies = NIL;

	fc_restrictinfo->left_ec = NULL;
	fc_restrictinfo->right_ec = NULL;
	fc_restrictinfo->left_em = NULL;
	fc_restrictinfo->right_em = NULL;
	fc_restrictinfo->scansel_cache = NIL;

	fc_restrictinfo->outer_is_left = false;

	fc_restrictinfo->hashjoinoperator = InvalidOid;

	fc_restrictinfo->left_bucketsize = -1;
	fc_restrictinfo->right_bucketsize = -1;
	fc_restrictinfo->left_mcvfreq = -1;
	fc_restrictinfo->right_mcvfreq = -1;

	fc_restrictinfo->left_hasheqoperator = InvalidOid;
	fc_restrictinfo->right_hasheqoperator = InvalidOid;

	return fc_restrictinfo;
}

/*
 * 递归地将子 RestrictInfo 节点插入布尔表达式。
 *
 * 我们将 RestrictInfos 放在简单（非 AND/OR）条件和子 OR 条件上，但不放在子 AND 条件上，因为没有必要。这看起来可能很奇怪，但它与我们在 RestrictInfo 列表的顶层使用隐式 AND 列表的事实密切相关。只有 OR 和简单条件是有效的 RestrictInfos。
 *
 * 相同的 is_pushed_down、outerjoin_delayed 和 pseudoconstant 标志值可以应用于结果中的所有 RestrictInfo 节点。同样适用于 security_level、outer_relids 和 nullable_relids。
 *
 * 给定的 required_relids 附加到我们的顶层输出，但任何 OR 条件成分都允许默认为仅包含的关系。
 */
static Expr * fc_make_sub_restrictinfos(PlannerInfo *fc_root,
					   Expr *fc_clause,
					   bool fc_is_pushed_down,
					   bool fc_outerjoin_delayed,
					   bool fc_pseudoconstant,
					   Index fc_security_level,
					   Relids fc_required_relids,
					   Relids fc_outer_relids,
					   Relids fc_nullable_relids)
{
	if (is_orclause(fc_clause))
	{
		List	   *fc_orlist = NIL;
		ListCell   *fc_temp;

		foreach(fc_temp, ((BoolExpr *) fc_clause)->args)
			fc_orlist = lappend(fc_orlist,
							 fc_make_sub_restrictinfos(fc_root,
													lfirst(fc_temp),
													fc_is_pushed_down,
													fc_outerjoin_delayed,
													fc_pseudoconstant,
													fc_security_level,
													NULL,
													fc_outer_relids,
													fc_nullable_relids));
		return (Expr *) fc_make_restrictinfo_internal(fc_root,
												   fc_clause,
												   make_orclause(fc_orlist),
												   fc_is_pushed_down,
												   fc_outerjoin_delayed,
												   fc_pseudoconstant,
												   fc_security_level,
												   fc_required_relids,
												   fc_outer_relids,
												   fc_nullable_relids);
	}
	else if (is_andclause(fc_clause))
	{
		List	   *fc_andlist = NIL;
		ListCell   *fc_temp;

		foreach(fc_temp, ((BoolExpr *) fc_clause)->args)
			fc_andlist = lappend(fc_andlist,
							  fc_make_sub_restrictinfos(fc_root,
													 lfirst(fc_temp),
													 fc_is_pushed_down,
													 fc_outerjoin_delayed,
													 fc_pseudoconstant,
													 fc_security_level,
													 fc_required_relids,
													 fc_outer_relids,
													 fc_nullable_relids));
		return make_andclause(fc_andlist);
	}
	else
		return (Expr *) fc_make_restrictinfo_internal(fc_root,
												   fc_clause,
												   NULL,
												   fc_is_pushed_down,
												   fc_outerjoin_delayed,
												   fc_pseudoconstant,
												   fc_security_level,
												   fc_required_relids,
												   fc_outer_relids,
												   fc_nullable_relids);
}

/*
 * commute_restrictinfo
 *
 * 给定一个包含二元操作条件的 RestrictInfo，产生一个表示该条件交换的 RestrictInfo。调用者必须传递交换运算符的 OID（假设它已经查找过，否则它不会知道这是有效的）。
 *
 * 请注意，结果与给定的 RestrictInfo 共享子结构。这对于与派生索引条件的意图使用是可以的，但如果源可能会更改，则可能是危险的。还要注意，我们在没有检查的情况下假设交换运算符是与原始操作属于同一 btree 和哈希运算符类的成员。
 */
RestrictInfo * commute_restrictinfo(RestrictInfo *fc_rinfo, Oid fc_comm_op)
{
	RestrictInfo *fc_result;
	OpExpr	   *fc_newclause;
	OpExpr	   *fc_clause = castNode(OpExpr, fc_rinfo->clause);

	Assert(list_length(fc_clause->args) == 2);

	/* 平坦复制所有字段... */
	fc_newclause = makeNode(OpExpr);
	memcpy(fc_newclause, fc_clause, sizeof(OpExpr));

	/* ...并调整我们需要更改以交换它的字段 */
	fc_newclause->opno = fc_comm_op;
	fc_newclause->opfuncid = InvalidOid;
	fc_newclause->args = list_make2(lsecond(fc_clause->args),
								 linitial(fc_clause->args));

	/* 同样，平坦复制所有字段的 rinfo ... */
	fc_result = makeNode(RestrictInfo);
	memcpy(fc_result, fc_rinfo, sizeof(RestrictInfo));

	/*
	 * ...并调整我们需要更改的字段。特别注意，我们可以保留任何缓存的选择性或成本估算，因为这些对于新条件应该是相同的。同样，我们可以保留源的 parent_ec。
	 */
	fc_result->clause = (Expr *) fc_newclause;
	fc_result->left_relids = fc_rinfo->right_relids;
	fc_result->right_relids = fc_rinfo->left_relids;
	Assert(fc_result->orclause == NULL);
	fc_result->left_ec = fc_rinfo->right_ec;
	fc_result->right_ec = fc_rinfo->left_ec;
	fc_result->left_em = fc_rinfo->right_em;
	fc_result->right_em = fc_rinfo->left_em;
	fc_result->scansel_cache = NIL;	/* 更新这个没有意义 */
	if (fc_rinfo->hashjoinoperator == fc_clause->opno)
		fc_result->hashjoinoperator = fc_comm_op;
	else
		fc_result->hashjoinoperator = InvalidOid;
	fc_result->left_bucketsize = fc_rinfo->right_bucketsize;
	fc_result->right_bucketsize = fc_rinfo->left_bucketsize;
	fc_result->left_mcvfreq = fc_rinfo->right_mcvfreq;
	fc_result->right_mcvfreq = fc_rinfo->left_mcvfreq;
	fc_result->left_hasheqoperator = InvalidOid;
	fc_result->right_hasheqoperator = InvalidOid;

	return fc_result;
}

/*
 * restriction_is_or_clause
 *
 * 如果 restrictinfo 节点包含一个 'or' 条件，则返回 true。
 */
bool restriction_is_or_clause(RestrictInfo *fc_restrictinfo)
{
	if (fc_restrictinfo->orclause != NULL)
		return true;
	else
		return false;
}

/*
 * restriction_is_securely_promotable
 *
 * 如果可以“提前”评估此条件，那就是在指定关系附加的其他限制条件之前，返回 true。
 */
bool restriction_is_securely_promotable(RestrictInfo *fc_restrictinfo,
								   RelOptInfo *fc_rel)
{
	/*
	 * 如果没有必须放在此之前的 baserestrictinfo 子句，*或者*如果这个是防漏的，则可以。
	 */
	if (fc_restrictinfo->security_level <= fc_rel->baserestrict_min_security ||
		fc_restrictinfo->leakproof)
		return true;
	else
		return false;
}

/*
 * get_actual_clauses
 *
 * 返回一个包含 'restrictinfo_list' 中裸子句的列表。
 *
 * 这仅在没有 RestrictInfos 可以是伪恒定子句
 * 的情况下使用（例如，在 indexqual 列表中是可以的）。
 */
List * get_actual_clauses(List *fc_restrictinfo_list)
{
	List	   *fc_result = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_restrictinfo_list)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		Assert(!fc_rinfo->pseudoconstant);

		fc_result = lappend(fc_result, fc_rinfo->clause);
	}
	return fc_result;
}

/*
 * extract_actual_clauses
 *
 * 从 'restrictinfo_list' 中提取裸子句，根据 'pseudoconstant'
 * 返回常规子句或伪恒定子句。
 */
List * extract_actual_clauses(List *fc_restrictinfo_list,
					   bool fc_pseudoconstant)
{
	List	   *fc_result = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_restrictinfo_list)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (fc_rinfo->pseudoconstant == fc_pseudoconstant)
			fc_result = lappend(fc_result, fc_rinfo->clause);
	}
	return fc_result;
}

/*
 * extract_actual_join_clauses
 *
 * 从 'restrictinfo_list' 中提取裸子句，将语义上与
 * 连接级别匹配的子句与被下移的子句分开。
 * 伪恒定子句不包含在结果中。
 *
 * 这仅在外连接中使用，因为对于普通连接，我们
 * 不关心下移性。
 */
void extract_actual_join_clauses(List *fc_restrictinfo_list,
							Relids fc_joinrelids,
							List **fc_joinquals,
							List **fc_otherquals)
{
	ListCell   *fc_l;

	*fc_joinquals = NIL;
	*fc_otherquals = NIL;

	foreach(fc_l, fc_restrictinfo_list)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (RINFO_IS_PUSHED_DOWN(fc_rinfo, fc_joinrelids))
		{
			if (!fc_rinfo->pseudoconstant)
				*fc_otherquals = lappend(*fc_otherquals, fc_rinfo->clause);
		}
		else
		{
			/* joinquals 不应该被标记为伪常量 */
			Assert(!fc_rinfo->pseudoconstant);
			*fc_joinquals = lappend(*fc_joinquals, fc_rinfo->clause);
		}
	}
}

/*
 * has_pseudoconstant_clauses
 *
 * 如果'restrictinfo_list'包括伪常量子句，则返回true。
 *
 * 这在我们决定是否允许扩展考虑在add_paths_to_joinrel()中向下推送连接时使用。
 */
bool has_pseudoconstant_clauses(PlannerInfo *fc_root,
						   List *fc_restrictinfo_list)
{
	ListCell   *fc_l;

	/* 如果我们知道没有伪常量，则无需查看 */
	if (!fc_root->hasPseudoConstantQuals)
		return false;

	/* 查看 RestrictInfo 列表中是否存在伪常量 */
	foreach(fc_l, fc_restrictinfo_list)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (fc_rinfo->pseudoconstant)
			return true;
	}
	return false;
}


/*
 * join_clause_is_movable_to
 *		测试一个连接条件是否是一个安全的参数化候选者，
 *		以便在指定的基础关系上进行扫描。
 *
 * 一个可移动的连接条件是可以安全地在其正常语义级别（即其所需的
 * relids）以下进行评估的条件，如果提供了它所需的来自其他关系的
 * 变量值。
 *
 * 我们坚持要求条件实际上引用目标关系；这可以防止不希望的
 * 弃用连接条件的移动，并确保条件可以移动到唯一的位置。
 *
 * 我们不能将外连接条件移动到它的外连接的非空侧，因为那将
 * 改变结果（行将被抑制而不是被空扩展）。
 *
 * 此外，该条件下方不得有外连接，这将使来自目标关系的变量无效。
 * 否则，该条件可能会给出与其正常语义级别下的结果不同的结果。
 *
 * 此外，连接条件不得使用任何对目标关系有 LATERAL 引用的关系，
 * 因为我们无法将其放置在与目标关系的嵌套循环的外侧。
 */
bool join_clause_is_movable_to(RestrictInfo *fc_rinfo, RelOptInfo *fc_baserel)
{
	/* 条件必须物理引用目标关系 */
	if (!bms_is_member(fc_baserel->relid, fc_rinfo->clause_relids))
		return false;

	/* 不能将外连接条件移动到连接的外侧 */
	if (bms_is_member(fc_baserel->relid, fc_rinfo->outer_relids))
		return false;

	/* 条件下方的目标关系不得为可空 */
	if (bms_is_member(fc_baserel->relid, fc_rinfo->nullable_relids))
		return false;

	/* 条件不得使用有 LATERAL 引用的关系 */
	if (bms_overlap(fc_baserel->lateral_referencers, fc_rinfo->clause_relids))
		return false;

	return true;
}

/*
 * join_clause_is_movable_into
 *		测试一个连接条件是否是可移动的，可以在
 *		当前连接上下文中进行评估。
 *
 * currentrelids：建议评估位置的 relids
 * current_and_outer：currentrelids 和 required_outer
 *		relids（参数化的外部关系）的并集
 *
 * 如果我们分别传递当前的 relids 和外部的 relids，同时在内部
 * 进行 bms_union，那么 API 将会更清晰；但是由于大多数调用者
 * 需要将此函数应用于多个条件，所以我们让调用者执行联合操作。
 *
 * 显然，条件只能引用来自当前关系及外部关系的变量。我们还检查
 * 它是否至少引用一个当前的变量，以确保条件将在参数化连接树中
 * 被推送到唯一的位置。我们还检查是否将条件推送到其外连接的外侧，
 * 或者向下推送到较低外连接的内部。
 *
 * 将条件向下推送到较低外连接的内部的检查只是近似的；有时当
 * 实际上在这里使用条件是安全的时，它会返回“false”，因为我们
 * 仍然在那个外连接以上。这是可以的，只要不同连接级别的答案
 * 是一致的：这只是意味着我们可能有时未能将条件推送到它可以
 * 安全推送的最远位置。目前尚不清楚是否值得更精确地做到这一点。
 * （但如果修复为完全准确，get_joinrel_parampathinfo() 中的一个
 * Assert 应该重新启用。）
 *
 * 在这里没有等效于 join_clause_is_movable_to 对 lateral_referencers
 * 的测试的检查。我们假设调用者不会询问，除非它已验证提议的外部
 * 关系没有对当前关系的 lateral 引用。（如果我们考虑将外部
 * 关系放在外侧而当前关系放在内侧的连接路径，那么在开始考虑时
 * 应该检查这一点；请参见 join_is_legal 和 joinpath.c 中的路径
 * 参数化检查。）另一方面，在 join_clause_is_movable_to 中，我们
 * 询问的是条件是否可以为某一有效的外部关系集移动，因此我们不
 * 能依赖于之前对 lateral 引用有效性的检查。
 *
 * 注意：如果返回 true，意味着条件可以移动到此连接关系，但这
 * 不意味着这是它可以移动到的最低连接。调用者可能需要进行额外
 * 调用以验证这一点，在提议的连接的任一输入上都不成功。
 *
 * 注意：get_joinrel_parampathinfo 依赖于如果 current_and_outer 为
 * NULL，则此函数将始终返回 false 的事实（因为前两个测试中的
 * 一个或另一个必须失败）。
 */
bool join_clause_is_movable_into(RestrictInfo *fc_rinfo,
							Relids fc_currentrelids,
							Relids fc_current_and_outer)
{
	/* 条件必须在可用的上下文中可评估 */
	if (!bms_is_subset(fc_rinfo->clause_relids, fc_current_and_outer))
		return false;

	/* 条件必须至少物理引用一个目标关系 */
	if (!bms_overlap(fc_currentrelids, fc_rinfo->clause_relids))
		return false;

	/* 不能将外连接条件移动到连接的外侧 */
	if (bms_overlap(fc_currentrelids, fc_rinfo->outer_relids))
		return false;

	/*
	 * 目标关系不得在条件下方为可空。 这是近似的，朝着安全的方向，
	 * 因为当前连接可能在会使其变为可空的连接之上，在这种情况下，
	 * 条件在这里将工作正常。但是我们没有足够的信息来确保这一点。
	 */
	if (bms_overlap(fc_currentrelids, fc_rinfo->nullable_relids))
		return false;

	return true;
}
